Khám phá chuyên sâu về bộ lập lịch kết xuất đồng thời của React và các kỹ thuật quản lý ngân sách thời gian khung hình tinh vi để xây dựng ứng dụng toàn cầu hiệu năng cao, đáp ứng nhanh.
Làm chủ Bộ lập lịch Kết xuất Đồng thời của React: Quản lý Ngân sách Thời gian Khung hình
Trong bối cảnh phát triển web không ngừng thay đổi, việc mang lại trải nghiệm người dùng (UX) mượt mà và nhạy bén là điều tối quan trọng. Người dùng trên toàn thế giới mong đợi các ứng dụng phải nhanh, trôi chảy và có tính tương tác, bất kể thiết bị, điều kiện mạng hay độ phức tạp của giao diện người dùng. Các framework JavaScript hiện đại, đặc biệt là React, đã có những bước tiến đáng kể trong việc giải quyết những yêu cầu này. Trọng tâm khả năng của React để đạt được điều này là Bộ lập lịch Kết xuất Đồng thời (Concurrent Rendering Scheduler) tinh vi, một cơ chế mạnh mẽ cho phép quản lý công việc kết xuất một cách thông minh hơn và, quan trọng hơn cả, là Ngân sách Thời gian Khung hình (Frame Time Budget) của nó.
Hướng dẫn toàn diện này sẽ đi sâu vào sự phức tạp của bộ lập lịch kết xuất đồng thời của React, tập trung cụ thể vào cách nó quản lý ngân sách thời gian khung hình. Chúng ta sẽ khám phá các nguyên tắc cơ bản, những thách thức mà nó giải quyết, và các chiến lược thực tế để các nhà phát triển tận dụng tính năng này để xây dựng các ứng dụng có hiệu năng cao và có thể truy cập toàn cầu.
Sự cấp thiết của việc Quản lý Ngân sách Thời gian Khung hình
Trước khi đi sâu vào cách triển khai cụ thể của React, điều cần thiết là phải hiểu tại sao việc quản lý ngân sách thời gian khung hình lại quan trọng đến vậy đối với các ứng dụng web hiện đại. Khái niệm "khung hình" (frame) đề cập đến một lần làm mới màn hình duy nhất. Ở hầu hết các màn hình, điều này xảy ra 60 lần mỗi giây, có nghĩa là mỗi khung hình có khoảng 16.67 mili giây (ms) để được kết xuất. Điều này thường được gọi là ngân sách 16ms.
Nếu một ứng dụng web mất nhiều thời gian hơn ngân sách này để kết xuất một khung hình, trình duyệt sẽ "bỏ qua" khung hình đó, dẫn đến giao diện người dùng bị giật, lag hoặc không phản hồi. Điều này ngay lập tức gây chú ý và khó chịu cho người dùng, đặc biệt là trong các thành phần tương tác như hoạt ảnh, cuộn trang hoặc nhập liệu vào biểu mẫu.
Thách thức trong Kết xuất Truyền thống:
- Các tác vụ chạy lâu: Trong kỷ nguyên trước khi có kết xuất đồng thời, React (và nhiều framework khác) hoạt động trên một luồng đồng bộ duy nhất. Nếu việc kết xuất của một thành phần mất quá nhiều thời gian, nó sẽ chặn luồng chính, ngăn cản các tương tác của người dùng (như nhấp chuột hoặc gõ phím) được xử lý cho đến khi việc kết xuất hoàn tất.
- Hiệu năng không thể đoán trước: Hiệu năng của một lần kết xuất có thể rất khó lường. Một thay đổi nhỏ về dữ liệu hoặc độ phức tạp của giao diện người dùng có thể dẫn đến thời gian kết xuất khác nhau rất nhiều, khiến việc đảm bảo trải nghiệm mượt mà trở nên khó khăn.
- Thiếu sự ưu tiên: Tất cả các tác vụ kết xuất đều được coi là quan trọng như nhau. Không có cơ chế sẵn có nào để ưu tiên các cập nhật khẩn cấp (ví dụ: nhập liệu của người dùng) hơn những cập nhật ít quan trọng hơn (ví dụ: tìm nạp dữ liệu trong nền).
Những thách thức này còn trở nên lớn hơn trong bối cảnh toàn cầu. Người dùng truy cập ứng dụng từ các khu vực có hạ tầng internet kém ổn định hơn hoặc các thiết bị cũ hơn phải đối mặt với những trở ngại lớn hơn nữa. Một ngân sách thời gian khung hình được quản lý kém có thể khiến một ứng dụng gần như không thể sử dụng được đối với một phần đáng kể người dùng toàn cầu.
Giới thiệu về Kết xuất Đồng thời của React
Chế độ Đồng thời của React (hiện là mặc định trong React 18) đã giới thiệu một sự thay đổi cơ bản trong cách React kết xuất ứng dụng. Ý tưởng cốt lõi là cho phép React gián đoạn, tạm dừng và tiếp tục kết xuất. Điều này đạt được thông qua một bộ lập lịch mới có nhận thức về quy trình kết xuất của trình duyệt và có thể ưu tiên các tác vụ một cách phù hợp.
Các khái niệm chính:
- Time Slicing (Phân chia thời gian): Bộ lập lịch chia nhỏ các tác vụ kết xuất đồng bộ, lớn thành các khối nhỏ hơn. Các khối này có thể được thực thi qua nhiều khung hình, cho phép React nhường quyền kiểm soát lại cho trình duyệt giữa các khối. Điều này đảm bảo rằng luồng chính vẫn khả dụng cho các tác vụ quan trọng như tương tác của người dùng và xử lý sự kiện.
- Tính tái nhập (Re-entrancy): React giờ đây có thể tạm dừng việc kết xuất giữa chừng trong vòng đời của một thành phần và tiếp tục nó sau đó, có thể theo một thứ tự khác hoặc sau khi các tác vụ khác đã hoàn thành. Điều này rất quan trọng để xen kẽ các loại cập nhật khác nhau.
- Mức độ ưu tiên: Bộ lập lịch gán mức độ ưu tiên cho các tác vụ kết xuất khác nhau. Ví dụ, các cập nhật khẩn cấp (như gõ vào một trường nhập liệu) nhận được mức độ ưu tiên cao hơn so với những cập nhật ít khẩn cấp hơn (như cập nhật danh sách được tìm nạp từ API).
Về cốt lõi, kết xuất đồng thời là về việc quản lý ngân sách thời gian khung hình bằng cách lập lịch và chia nhỏ công việc một cách thông minh.
Bộ lập lịch React: Động cơ của Kết xuất Đồng thời
Bộ lập lịch React là người điều phối đằng sau kết xuất đồng thời. Nó chịu trách nhiệm quyết định khi nào cần kết xuất, kết xuất cái gì, và làm thế nào để chia nhỏ công việc để phù hợp với ngân sách thời gian khung hình. Nó tương tác với các API requestIdleCallback và requestAnimationFrame của trình duyệt để lập lịch các tác vụ một cách hiệu quả.
Cách thức hoạt động:
- Hàng đợi tác vụ: Bộ lập lịch duy trì một hàng đợi các tác vụ (ví dụ: cập nhật thành phần, trình xử lý sự kiện).
- Các mức độ ưu tiên: Mỗi tác vụ được gán một mức độ ưu tiên. React có một hệ thống các mức độ ưu tiên rời rạc, từ cao nhất (ví dụ: nhập liệu của người dùng) đến thấp nhất (ví dụ: tìm nạp dữ liệu nền).
- Quyết định lập lịch: Khi trình duyệt rảnh rỗi (tức là có thời gian trong ngân sách khung hình), bộ lập lịch sẽ chọn tác vụ có mức độ ưu tiên cao nhất từ hàng đợi và lên lịch thực thi nó.
- Time Slicing trong thực tế: Nếu một tác vụ quá lớn để hoàn thành trong thời gian còn lại của khung hình hiện tại, bộ lập lịch sẽ "cắt lát" nó. Nó thực hiện một phần công việc, sau đó nhường lại cho trình duyệt, lên lịch phần còn lại của công việc cho một khung hình trong tương lai.
- Gián đoạn và Tiếp tục: Nếu một tác vụ có mức độ ưu tiên cao hơn xuất hiện trong khi một tác vụ có mức độ ưu tiên thấp hơn đang được xử lý, bộ lập lịch có thể gián đoạn tác vụ có mức độ ưu tiên thấp hơn, xử lý tác vụ có mức độ ưu tiên cao hơn, và sau đó tiếp tục tác vụ đã bị gián đoạn sau này.
Việc lập lịch động này cho phép React đảm bảo rằng các cập nhật quan trọng nhất được xử lý trước, ngăn chặn luồng chính bị chặn và giữ cho giao diện người dùng luôn nhạy bén.
Hiểu về Quản lý Ngân sách Thời gian Khung hình trong Thực tế
Mục tiêu chính của bộ lập lịch là đảm bảo rằng công việc kết xuất không vượt quá thời gian khung hình có sẵn. Điều này bao gồm một số chiến lược chính:
1. Phân chia thời gian (Time Slicing) của công việc
Khi React cần thực hiện một hoạt động kết xuất đáng kể, chẳng hạn như kết xuất một cây thành phần lớn hoặc xử lý một cập nhật trạng thái phức tạp, bộ lập lịch sẽ can thiệp. Thay vì hoàn thành toàn bộ hoạt động trong một lần (có thể mất nhiều mili giây và vượt quá ngân sách 16ms), nó chia công việc thành các đơn vị nhỏ hơn.
Ví dụ: Hãy tưởng tượng một danh sách lớn các mục cần được kết xuất. Trong mô hình đồng bộ, React sẽ cố gắng kết xuất tất cả các mục cùng một lúc. Nếu việc này mất 50ms, giao diện người dùng sẽ bị đóng băng trong khoảng thời gian đó. Với time slicing, React có thể kết xuất 10 mục đầu tiên, sau đó nhường quyền. Trong khung hình tiếp theo, nó kết xuất 10 mục tiếp theo, và cứ thế tiếp tục. Điều này có nghĩa là người dùng thấy danh sách xuất hiện dần dần, nhưng giao diện người dùng vẫn nhạy bén trong suốt quá trình.
Bộ lập lịch liên tục theo dõi thời gian đã trôi qua. Nếu phát hiện rằng nó sắp hết ngân sách của khung hình, nó sẽ tạm dừng công việc hiện tại và lên lịch phần còn lại cho cơ hội khả dụng tiếp theo.
2. Ưu tiên các Cập nhật
Bộ lập lịch của React gán các mức độ ưu tiên khác nhau cho các loại cập nhật khác nhau. Điều này cho phép nó trì hoãn công việc ít quan trọng hơn để ưu tiên cho các cập nhật quan trọng hơn.
Các Mức độ ưu tiên (Khái niệm):
- `Immediate` (Cao nhất): Dành cho những thứ như nhập liệu của người dùng đòi hỏi phản hồi tức thì.
- `UserBlocking` (Cao): Dành cho các cập nhật giao diện người dùng quan trọng mà người dùng đang chờ đợi, chẳng hạn như một modal xuất hiện hoặc xác nhận gửi biểu mẫu.
- `Normal` (Trung bình): Dành cho các cập nhật ít quan trọng hơn, như kết xuất một danh sách các mục không nằm ngay trong tầm nhìn.
- `Low` (Thấp): Dành cho các tác vụ nền, chẳng hạn như tìm nạp dữ liệu không ảnh hưởng trực tiếp đến tương tác tức thì của người dùng.
- `Offscreen` (Thấp nhất): Dành cho các thành phần hiện không hiển thị với người dùng.
Khi một cập nhật có mức độ ưu tiên cao xảy ra (ví dụ: người dùng nhấp vào một nút), bộ lập lịch sẽ ngay lập tức gián đoạn bất kỳ công việc có mức độ ưu tiên thấp hơn nào có thể đang được tiến hành. Điều này đảm bảo rằng giao diện người dùng phản hồi ngay lập tức với các hành động của người dùng, điều này rất quan trọng đối với các ứng dụng được sử dụng bởi các nhóm dân số đa dạng với tốc độ mạng và khả năng thiết bị khác nhau.
3. Các tính năng Đồng thời và Tác động của chúng
React 18 đã giới thiệu một số tính năng tận dụng kết xuất đồng thời và khả năng quản lý ngân sách thời gian khung hình của nó:
startTransition: API này cho phép bạn đánh dấu một số cập nhật trạng thái là "chuyển tiếp" (transitions). Chuyển tiếp là các cập nhật không khẩn cấp không cần phải chặn giao diện người dùng. Điều này hoàn hảo cho các hoạt động như lọc một danh sách lớn hoặc điều hướng giữa các trang, nơi sự chậm trễ nhỏ trong việc cập nhật giao diện người dùng là chấp nhận được. Bộ lập lịch sẽ ưu tiên giữ cho giao diện người dùng nhạy bén và sẽ kết xuất cập nhật chuyển tiếp trong nền.useDeferredValue: Tương tự nhưstartTransition,useDeferredValuecho phép bạn trì hoãn việc cập nhật một phần của giao diện người dùng. Điều này hữu ích cho các tính toán hoặc kết xuất tốn kém có thể bị trì hoãn mà không ảnh hưởng tiêu cực đến trải nghiệm người dùng. Ví dụ, nếu người dùng đang gõ vào hộp tìm kiếm, bạn có thể trì hoãn việc kết xuất kết quả tìm kiếm cho đến khi người dùng gõ xong hoặc có một khoảng dừng ngắn.- Gộp tự động (Automatic Batching): Trong các phiên bản trước của React, nhiều cập nhật trạng thái trong một trình xử lý sự kiện đã được gộp lại với nhau. Tuy nhiên, các cập nhật từ promises, timeouts, hoặc các trình xử lý sự kiện gốc không được gộp. React 18 tự động gộp tất cả các cập nhật trạng thái, bất kể nguồn gốc của chúng, giúp giảm đáng kể số lần kết xuất lại và cải thiện hiệu năng. Điều này gián tiếp giúp quản lý ngân sách thời gian khung hình bằng cách giảm tổng công việc kết xuất.
Những tính năng này là yếu tố thay đổi cuộc chơi để xây dựng các ứng dụng toàn cầu. Một người dùng ở khu vực có băng thông thấp có thể trải nghiệm điều hướng và tương tác mượt mà hơn, vì bộ lập lịch quản lý một cách thông minh khi nào và làm thế nào các cập nhật được áp dụng.
Chiến lược Tối ưu hóa Ứng dụng của bạn với Kết xuất Đồng thời
Mặc dù bộ lập lịch của React xử lý phần lớn công việc nặng nhọc, các nhà phát triển có thể và nên sử dụng các chiến lược để tối ưu hóa thêm ứng dụng của họ và đảm bảo chúng hoạt động tốt trên toàn cầu.
1. Xác định và Cô lập các Tính toán Tốn kém
Bước đầu tiên là xác định các thành phần hoặc hoạt động tốn nhiều tài nguyên tính toán. Các công cụ như React DevTools Profiler là vô giá để xác định các điểm nghẽn hiệu năng.
Hành động cụ thể: Sau khi xác định, hãy xem xét việc ghi nhớ (memoizing) các tính toán tốn kém bằng cách sử dụng React.memo cho các thành phần hoặc useMemo cho các giá trị. Tuy nhiên, hãy thận trọng; việc ghi nhớ quá mức cũng có thể gây ra chi phí phụ.
2. Tận dụng startTransition và useDeferredValue một cách Thích hợp
Những tính năng đồng thời này là những người bạn tốt nhất của bạn để quản lý các cập nhật không quan trọng.
Ví dụ: Hãy xem xét một bảng điều khiển có nhiều widget. Nếu người dùng lọc một bảng trong một widget, hoạt động lọc đó có thể tốn nhiều tài nguyên tính toán. Thay vì chặn toàn bộ bảng điều khiển, hãy bọc cập nhật trạng thái kích hoạt việc lọc trong startTransition. Điều này đảm bảo người dùng vẫn có thể tương tác với các widget khác trong khi bảng đang lọc.
Ví dụ (Bối cảnh Toàn cầu): Một trang web thương mại điện tử đa quốc gia có thể có một trang danh sách sản phẩm nơi việc áp dụng các bộ lọc có thể mất thời gian. Sử dụng startTransition cho cập nhật bộ lọc đảm bảo rằng các yếu tố giao diện người dùng khác, như điều hướng hoặc các nút "thêm vào giỏ hàng", vẫn nhạy bén, mang lại trải nghiệm tốt hơn cho người dùng có kết nối chậm hơn hoặc thiết bị kém mạnh mẽ hơn.
3. Giữ cho các Thành phần Nhỏ và Tập trung
Các thành phần nhỏ hơn, tập trung hơn sẽ dễ dàng hơn cho bộ lập lịch quản lý. Khi một thành phần nhỏ, thời gian kết xuất của nó thường ngắn hơn, giúp dễ dàng vừa vặn trong ngân sách khung hình.
Hành động cụ thể: Phân rã các thành phần lớn, phức tạp thành các thành phần nhỏ hơn, có thể tái sử dụng. Điều này không chỉ cải thiện hiệu năng mà còn tăng cường khả năng bảo trì và tái sử dụng mã nguồn trong đội ngũ phát triển toàn cầu của bạn.
4. Tối ưu hóa Tìm nạp Dữ liệu và Quản lý Trạng thái
Cách bạn tìm nạp và quản lý dữ liệu có thể ảnh hưởng đáng kể đến hiệu năng kết xuất. Việc tìm nạp dữ liệu không hiệu quả có thể dẫn đến việc kết xuất lại không cần thiết hoặc một lượng lớn dữ liệu được xử lý đồng thời.
Hành động cụ thể: Triển khai các chiến lược tìm nạp dữ liệu hiệu quả, chẳng hạn như phân trang, tải lười (lazy loading) và chuẩn hóa dữ liệu. Các thư viện như React Query hoặc Apollo Client có thể giúp quản lý trạng thái máy chủ một cách hiệu quả, giảm gánh nặng cho các thành phần của bạn và bộ lập lịch.
5. Tách mã (Code Splitting) và Tải lười (Lazy Loading)
Đối với các ứng dụng lớn, đặc biệt là những ứng dụng nhắm đến đối tượng toàn cầu nơi băng thông có thể là một hạn chế, việc tách mã và tải lười là rất cần thiết. Điều này đảm bảo rằng người dùng chỉ tải xuống mã JavaScript họ cần cho chế độ xem hiện tại.
Ví dụ: Một công cụ báo cáo phức tạp có thể có nhiều mô-đun khác nhau. Bằng cách sử dụng React.lazy và Suspense, bạn có thể tải các mô-đun này theo yêu cầu. Điều này làm giảm thời gian tải ban đầu và cho phép bộ lập lịch tập trung vào việc kết xuất các phần hiển thị của ứng dụng trước tiên.
6. Phân tích Hiệu năng (Profiling) và Tối ưu hóa Lặp đi lặp lại
Tối ưu hóa hiệu năng là một quá trình liên tục. Thường xuyên phân tích hiệu năng ứng dụng của bạn, đặc biệt là sau khi giới thiệu các tính năng mới hoặc thực hiện các thay đổi đáng kể.
Hành động cụ thể: Sử dụng React DevTools Profiler trong các bản dựng sản xuất (hoặc trong môi trường staging mô phỏng sản xuất) để xác định sự suy giảm hiệu năng. Tập trung vào việc hiểu thời gian được dành ở đâu trong quá trình kết xuất và cách bộ lập lịch quản lý các tác vụ đó.
Những Lưu ý Toàn cầu và các Phương pháp Tốt nhất
Khi xây dựng ứng dụng cho đối tượng toàn cầu, việc quản lý ngân sách thời gian khung hình trở nên quan trọng hơn bao giờ hết. Sự đa dạng của môi trường người dùng đòi hỏi một cách tiếp cận chủ động đối với hiệu năng.
1. Độ trễ Mạng và Băng thông
Người dùng ở các khu vực khác nhau trên thế giới sẽ trải nghiệm các điều kiện mạng rất khác nhau. Các ứng dụng phụ thuộc nhiều vào việc truyền dữ liệu lớn và thường xuyên sẽ hoạt động kém ở các khu vực có băng thông thấp.
Phương pháp tốt nhất: Tối ưu hóa tải trọng dữ liệu (data payloads), sử dụng các cơ chế bộ nhớ đệm (caching), và xem xét các chiến lược ưu tiên ngoại tuyến (offline-first) khi thích hợp. Đảm bảo rằng các tính toán tốn kém phía máy khách được xử lý hiệu quả bởi bộ lập lịch, thay vì dựa vào giao tiếp liên tục với máy chủ.
2. Khả năng của Thiết bị
Phạm vi các thiết bị được sử dụng trên toàn thế giới rất đa dạng, từ điện thoại thông minh và máy tính để bàn cao cấp đến các máy tính và máy tính bảng cũ, kém mạnh mẽ hơn.
Phương pháp tốt nhất: Thiết kế với ý tưởng suy giảm từ từ (graceful degradation). Sử dụng các tính năng đồng thời để đảm bảo rằng ngay cả trên các thiết bị kém mạnh mẽ hơn, ứng dụng vẫn có thể sử dụng được và nhạy bén. Tránh các hoạt ảnh hoặc hiệu ứng nặng về tính toán trừ khi chúng cần thiết và đã được kiểm tra kỹ lưỡng về hiệu năng trên nhiều loại thiết bị.
3. Quốc tế hóa (i18n) và Bản địa hóa (l10n)
Mặc dù không liên quan trực tiếp đến bộ lập lịch, quá trình quốc tế hóa và bản địa hóa ứng dụng của bạn có thể gây ra các vấn đề về hiệu năng. Các tệp dịch lớn hoặc logic định dạng phức tạp có thể làm tăng chi phí kết xuất.
Phương pháp tốt nhất: Tối ưu hóa các thư viện i18n/l10n của bạn và đảm bảo rằng mọi bản dịch được tải động đều được xử lý hiệu quả. Bộ lập lịch có thể giúp bằng cách trì hoãn việc kết xuất nội dung đã bản địa hóa nếu nó không hiển thị ngay lập tức.
4. Kiểm thử trên các Môi trường Đa dạng
Điều quan trọng là phải kiểm thử ứng dụng của bạn trong các môi trường mô phỏng điều kiện thực tế toàn cầu.
Phương pháp tốt nhất: Sử dụng các công cụ dành cho nhà phát triển của trình duyệt để mô phỏng các điều kiện mạng và loại thiết bị khác nhau. Nếu có thể, hãy tiến hành kiểm thử người dùng với các cá nhân từ các vị trí địa lý khác nhau và với các cấu hình phần cứng khác nhau.
Tương lai của Kết xuất trong React
Hành trình của React với kết xuất đồng thời vẫn đang phát triển. Khi hệ sinh thái trưởng thành và nhiều nhà phát triển hơn nắm bắt các mô hình mới này, chúng ta có thể mong đợi những công cụ và kỹ thuật tinh vi hơn nữa để quản lý hiệu năng kết xuất.
Sự nhấn mạnh vào quản lý ngân sách thời gian khung hình là một minh chứng cho cam kết của React trong việc cung cấp trải nghiệm người dùng chất lượng cao cho tất cả người dùng, ở mọi nơi. Bằng cách hiểu và áp dụng các nguyên tắc của kết xuất đồng thời và các cơ chế lập lịch của nó, các nhà phát triển có thể xây dựng các ứng dụng không chỉ giàu tính năng mà còn có hiệu năng và khả năng đáp ứng vượt trội, bất kể vị trí hoặc thiết bị của người dùng.
Kết luận
Bộ lập lịch Kết xuất Đồng thời của React, với khả năng quản lý ngân sách thời gian khung hình tinh vi, đại diện cho một bước tiến đáng kể trong việc xây dựng các ứng dụng web hiệu năng cao. Bằng cách chia nhỏ công việc, ưu tiên các cập nhật, và kích hoạt các tính năng như chuyển tiếp và giá trị trì hoãn, React đảm bảo rằng giao diện người dùng vẫn nhạy bén ngay cả trong các hoạt động kết xuất phức tạp.
Đối với đối tượng toàn cầu, công nghệ này không chỉ là một sự tối ưu hóa; nó là một sự cần thiết. Nó thu hẹp khoảng cách được tạo ra bởi các điều kiện mạng, khả năng thiết bị và kỳ vọng của người dùng khác nhau. Bằng cách tích cực tận dụng các tính năng đồng thời, tối ưu hóa việc xử lý dữ liệu, và duy trì sự tập trung vào hiệu năng thông qua việc phân tích và kiểm thử, các nhà phát triển có thể tạo ra những trải nghiệm người dùng thực sự đặc biệt làm hài lòng người dùng trên toàn thế giới.
Làm chủ bộ lập lịch của React là chìa khóa để khai phá toàn bộ tiềm năng của phát triển web hiện đại. Hãy nắm bắt sự đồng thời, và xây dựng các ứng dụng nhanh, mượt mà và dễ tiếp cận cho tất cả mọi người.